package com.junhong.forum.service;

import java.util.List;

import javax.ejb.Asynchronous;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.inject.Inject;
import javax.interceptor.Interceptors;

import org.slf4j.Logger;

import com.junhong.auth.annotation.OwnerCheck;
import com.junhong.auth.annotation.Role;
import com.junhong.auth.entity.RoleType;
import com.junhong.auth.entity.User;
import com.junhong.forum.common.AuthorizationInterceptor;
import com.junhong.forum.dao.ForumThreadDAO;
import com.junhong.forum.entity.ForumCategory;
import com.junhong.forum.entity.ForumReply;
import com.junhong.forum.entity.ForumThread;
import com.junhong.forum.entity.ThreadDashBoard;
import com.junhong.forum.exceptions.AuthorizationFailException;

/**
 * Session Bean implementation class ThreadEjb
 */
@Stateless
@LocalBean
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ThreadEjb {

	@Inject
	private ForumThreadDAO		threadDAO;

	@EJB
	private DashBoardService	dashBoardEjb;

	/*
	 * @Inject UserTransaction userTransaction;
	 */

	@Inject
	Logger						logger;

	/**
	 * Default constructor.
	 */
	public ThreadEjb() {
	}

	/** business method */

	/**
	 * @param id
	 * @return
	 */
	public ForumThread getForumThreadById(int id) {
		return threadDAO.findById(ForumThread.class, id);
	}

	/**
	 * @param id
	 * @return
	 */
	public ForumThread getLoadedThreadById(int id) {
		ForumThread loadedThread = threadDAO.findById(ForumThread.class, id);
		loadedThread.getForumReplyList().size();
		return loadedThread;
	}

	/**
	 * @param category
	 * @return
	 */
	public List<ForumThread> getForumThreadListByCategory(ForumCategory category) {
		return threadDAO.getThreadsByCategory(category);
	}

	/**
	 * @param category
	 * @return
	 */
	public int getForumThreadCountByCategory(ForumCategory category, boolean isUserAccessableToLockedThreads) {
		return (int) threadDAO.getTotalCount(category, isUserAccessableToLockedThreads);
	}

	/**
	 * @param category
	 * @return
	 */
	public int getForumThreadCountByOwner(User owner, boolean isUserAccessableToLockedThreads) {
		return (int) threadDAO.getTotalCount(owner, isUserAccessableToLockedThreads);
	}

	/**
	 * @param forumCategory
	 * @param start
	 * @param size
	 * @return
	 */
	public List<ForumThread> getThreadListByCategory(ForumCategory forumCategory, int start, int size, List<Integer> excludeshreadsId, boolean isUserAccessableToLockedThreads) {
		List<ForumThread> forumThreadList = null;
		logger.info("Start ------ " + "getThreadListByCategory ");
		forumThreadList = threadDAO.getThreadsByCategory(forumCategory, start, size, excludeshreadsId, isUserAccessableToLockedThreads);
		logger.info("End ------ " + "getThreadListByCategory ");
		return forumThreadList;
	}

	/**
	 * get all the threads for the given User
	 * 
	 * @param user
	 * @param start
	 * @param size
	 * @param excludeshreadsId
	 * @param isUserAccessableToLockedThreads
	 * @return
	 */
	public List<ForumThread> getThreadList(User user, int start, int size) {
		List<ForumThread> forumThreadList = null;
		logger.info("Start ------ " + "getThreadListByCategory ");
		forumThreadList = threadDAO.getThreadsByOwner(user, start, size, false);
		logger.info("End ------ " + "getThreadListByCategory ");
		return forumThreadList;
	}

	/**
	 * create a new Thread
	 * 
	 * @param forumThread
	 */

	@Role.List({ @Role(RoleType.REGISTERED), @Role(RoleType.CATEGORY_OWNER), @Role(RoleType.SYSADMIN) })
	@Interceptors(AuthorizationInterceptor.class)
	public void createForumThread(ForumThread forumThread) throws AuthorizationFailException {
		threadDAO.create(forumThread);
	}

	/**
	 * update Thread
	 * 
	 * @param forumThread
	 */
	@Role.List({ @Role(RoleType.CATEGORY_OWNER), @Role(RoleType.SYSADMIN) })
	@OwnerCheck
	@Interceptors(AuthorizationInterceptor.class)
	public void updateForumThread(ForumThread forumThread) throws AuthorizationFailException {
		threadDAO.update(forumThread);
	}

	public void updateForumThreadWithAuthorization(ForumThread forumThread) {
		threadDAO.update(forumThread);
	}

	/**
	 * delete thread
	 * 
	 * @param forumThread
	 */
	@Role.List({ @Role(RoleType.CATEGORY_OWNER), @Role(RoleType.SYSADMIN) })
	@Interceptors(AuthorizationInterceptor.class)
	public void deleteForumThread(ForumThread forumThread) throws AuthorizationFailException {
		threadDAO.delete(forumThread);
	}

	public void refreshThead(ForumThread forumThread) {
		threadDAO.refresh(forumThread);
	}

	public void updateNumOfView(int id, int numOfHit) {
		threadDAO.updateNumOfView(id, numOfHit);
	}

	/**
	 * @param thread
	 */
	public ForumThread findByIdWithPessimisticWrite(int id) {

		return threadDAO.findByIdWithPessimisticWrite(ForumThread.class, id);

	}

	/**
	 * use pessimistic write locking to lock the row so that numofReplies can be
	 * update sequencially while it still can be read
	 * 
	 * @param thread
	 */
	@Asynchronous
	public void updateNumOfRepliesNSetLastReply(ForumThread thread, ForumReply lastReply, int delta) {

		thread = findByIdWithPessimisticWrite(thread.getId());
		ForumReply newLastReply = lastReply;
		// for delete, set it to null. because it is gonna to be slow if search
		// the previous one
		if (delta == -1) {
			if (thread.getLastReply().equals(lastReply)) {
				thread.setLastReply(null);
			}
		} else {
			// for create,
			thread.setLastReply(newLastReply);
		}

		thread.setNumberOfReplies(thread.getNumberOfReplies() + delta);
		threadDAO.update(thread);

	}

	public void updateVotes(int id, int votes) {
		threadDAO.updateVotesNRating(id, votes);
	}

	public ForumThread getThreadWithLoadedStickiedByUserById(int id) {
		ForumThread loadedThread = threadDAO.findById(ForumThread.class, id);
		// loadedThread.getStickiedByUsers().size();
		return loadedThread;
	}

	public List<ForumThread> getToppedThreadList(ForumCategory category) {

		return threadDAO.getToppedThreadList(category);

	}

	/**
	 * put thread on the main dashboard
	 * 
	 * @param thread
	 */
	@Role.List({ @Role(RoleType.CATEGORY_OWNER), @Role(RoleType.SYSADMIN) })
	@Interceptors(AuthorizationInterceptor.class)
	public void dashBoardThread(ForumThread thread) throws AuthorizationFailException {

		thread = this.findByIdWithPessimisticWrite(thread.getId());
		thread.setOnDashBoard(true);
		this.updateForumThread(thread);
		// insert it into the dashboard table
		ThreadDashBoard db = new ThreadDashBoard();
		db.setCategoryId(thread.getCategory().getId());
		db.setThreadId(thread.getId());
		db.setThreadSubject(thread.getSubject());
		db.setThreadOwner(thread.getOwner().getUserId());
		dashBoardEjb.create(db);

	}

	@Role.List({ @Role(RoleType.CATEGORY_OWNER), @Role(RoleType.SYSADMIN) })
	@Interceptors(AuthorizationInterceptor.class)
	public void unDashBoardThread(ForumThread thread) throws AuthorizationFailException {

		if (!thread.isOnDashBoard()) {
			return;
		}
		thread = this.findByIdWithPessimisticWrite(thread.getId());
		thread.setOnDashBoard(false);
		this.updateForumThread(thread);

		// remove it from dashboard table
		ThreadDashBoard dashBoard = dashBoardEjb.getDashBoard(thread.getCategory().getId(), thread.getId());
		if (dashBoard != null) {
			dashBoardEjb.delete(dashBoard);
		}

	}

	public List<ForumThread> getMostRecentThread(int size) {
		String hqlMostRecent = "select threads from ForumThread threads  where threads.onDashBoard=false order by threads.createTime desc ";
		return threadDAO.findByHQL(hqlMostRecent, ForumThread.class, 0, size);
	}

}
